# Copyright (c) 2008 Simon Menke
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require "preload_parent_resources"

module SexyNestedResources # :nodoc:
  
  module NamedRouteCollection # :nodoc:
    def self.included(base) # :nodoc:
      base.send :include, InstanceMethods
    end
    
    module InstanceMethods #:nodoc:
      def self.included(base) # :nodoc:
        base.class_eval do
          alias_method_chain :define_url_helper, :sexy_nested_resources
          alias_method_chain :install, :sexy_nested_resources
        end
      end
      
    private
      def combined_helpers(reset=false)
        @combined_helpers = nil if reset
        @combined_helpers ||= {}
      end
      
      def define_url_helper_with_sexy_nested_resources(route, name, kind, options)
        options[:parent_resource_types] ||= []
        
        define_url_helper_without_sexy_nested_resources(route, name, kind, options)
        
        combined_helper_selector = name.to_s
        (options[:parent_resource_types] + ["root"]).each do |r|
          combined_helper_selector.sub!(Regexp.new("^((formatted_)?(.+_)?)#{r}_"),'\1')
        end
        
        combined_helpers[kind] ||= {}
        combined_helpers[kind][combined_helper_selector] ||= []
        combined_helpers[kind][combined_helper_selector] <<  [options[:parent_resource_types], name]
      end
      
      def combined_helper_name(name, kind)
        "#{name}_#{kind}"
      end
      
      def define_combined_helper(name, kind, comb_helpers)
        
        return if name == "root"
        
        selector = combined_helper_name(name, kind)
        
        comb_helpers.sort! {|a,b| b[0].size <=> a[0].size }
        body = comb_helpers.collect do |h|
          if h[0].empty?
            branch = "return #{h[1]}_#{kind}(*args)\n"
          else
            conditions = Array.new
            additional_args = Array.new
            h[0].each_with_index do |p,i|
              conditions      << "(parent_resource_types[#{i}] == #{p.inspect})"
              additional_args << "request.parameters[\"#{p}_id\"]" 
            end
            conditions = conditions.join(" && ")
            additional_args = additional_args.join(", ")
            
            branch = "return #{h[1]}_#{kind}(#{additional_args}, *args) if #{conditions}"
          end
          branch
        end.join("\n")
        
        @module.module_eval <<-end_eval # We use module_eval to avoid leaks
          def #{selector}(*args)
            parent_resource_types = request.parent_resource_types
            #{body}
          end
          protected :#{selector}
        end_eval
        
        helpers << selector
      end
      
      def install_with_sexy_nested_resources(destinations = [::ActionController::Base, ::ActionView::Base], regenerate = false)
        reset! if regenerate
        if regenerate or @sexy_nested_resources_initialized.nil?
          (combined_helpers[:url]  || []).each { |name, helpers| define_combined_helper(name, :url,  helpers) }
          (combined_helpers[:path] || []).each { |name, helpers| define_combined_helper(name, :path, helpers) }
          combined_helpers(true)
          @sexy_nested_resources_initialized = true
        end
        Array(destinations).each do |dest|
          dest.send! :include, @module
        end
      end
      
    end
  end
  
  module AbstractRequest # :nodoc:
    def self.included(base) # :nodoc:
      base.send :include, InstanceMethods
    end
    
    module InstanceMethods
      def self.included(base) # :nodoc:
        base.class_eval do
          alias_method_chain :path_parameters=, :sexy_nested_resources
        end
      end
      
      def path_parameters_with_sexy_nested_resources=(parameters) #:nodoc:
        @parent_resource_types = parameters.delete(:parent_resource_types)
        send :path_parameters_without_sexy_nested_resources=, parameters
      end
      
      # returns the parent resource types ordered by there level in the request url.
      #  when: request.path          # => /projects/231/team_members/45/tasks
      #  then: parent_resource_types # => [:project, :team_member]
      #
      def parent_resource_types
        @parent_resource_types ||= []
      end
    end
  end
  
  module Resource # :nodoc:
    def self.included(base) # :nodoc:
      base.send :include, InstanceMethods
    end
    
    module InstanceMethods #:nodoc:
      def self.included(base) # :nodoc:
        base.class_eval do
          alias_method_chain :requirements, :sexy_nested_resources
          alias_method_chain :set_prefixes, :sexy_nested_resources
          alias_method_chain :nesting_name_prefix, :sexy_nested_resources
          alias_method_chain :initialize, :sexy_nested_resources
        end
      end
      
      def initialize_with_sexy_nested_resources(entities, options)
        initialize_without_sexy_nested_resources(entities, options)
      end
      
      def parent_resource_types
        @parent_resource_types ||= @options[:parent_resource_types] || []
      end
      
      def nesting_parent_resource_types
        @nesting_parent_resource_types ||= (parent_resource_types.dup << singular)
      end
      
      def requirements_with_sexy_nested_resources(with_id = false)
        requirements_without_sexy_nested_resources(with_id).merge({:parent_resource_types => parent_resource_types})
      end

      def nesting_name_prefix_with_sexy_nested_resources
        "#{@nesting_name_prefix}#{singular}_"
      end
    
    private
      
      def set_prefixes_with_sexy_nested_resources
        set_prefixes_without_sexy_nested_resources
        @nesting_name_prefix = @name_prefix
        @name_prefix = "root_#{name_prefix}" if parent_resource_types.empty?
      end
      
    end
  end
  
  module Resources # :nodoc:
    def self.included(base)
      base.send :include, InstanceMethods
    end
    
    module InstanceMethods #:nodoc:
      def self.included(base)
        base.class_eval do
          alias_method_chain :map_resource, :sexy_nested_resources
          alias_method_chain :map_singleton_resource, :sexy_nested_resources
        end
      end
      
    private
      
      def map_resource_with_sexy_nested_resources(entities, options = {}, &block)
        resource = ::ActionController::Resources::Resource.new(entities, options)
        
        with_options :controller => resource.controller do |map|
          map_collection_actions(map, resource)
          map_default_collection_actions(map, resource)
          map_new_actions(map, resource)
          map_member_actions(map, resource)
          
          map_associations(resource, options)
          
          if block_given?
            with_options(:parent_resource_types => resource.nesting_parent_resource_types, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
          end
        end
      end
      
      def map_singleton_resource_with_sexy_nested_resources(entities, options = {}, &block)
        resource = ::ActionController::Resources::SingletonResource.new(entities, options)
        
        with_options :controller => resource.controller do |map|
          map_collection_actions(map, resource)
          map_default_singleton_actions(map, resource)
          map_new_actions(map, resource)
          map_member_actions(map, resource)
          
          map_associations(resource, options)
          
          if block_given?
            with_options(:parent_resource_types => resource.nesting_parent_resource_types, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
          end
        end
      end
      
    end
  end
end
